package utility;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.Vector;

import javax.naming.directory.Attribute;

import org.w3c.dom.Attr;

public class SymbolTable {
    private int depth;
    private ArrayList scopeDisplay;
    private Hashtable<String, SymbolEntry> hashTable;

    public SymbolTable() {
        depth = 0;
        scopeDisplay = new ArrayList();
        scopeDisplay.add(null);
        hashTable = new Hashtable<String, SymbolEntry>();
    }

    public void openScope() {
        depth++;
        scopeDisplay.add(null);
    }

    public void closeScope() {
        SymbolEntry sym = (SymbolEntry) scopeDisplay.get(depth);
        while (sym != null) {
            SymbolEntry prevSym = sym.getVar();
            deleteSym(sym);
            if (prevSym != null)
                addSym(prevSym);
            sym = sym.getLevel();
        }
        depth--;
    }

    public SymbolEntry retrieveSymbol(String name) {
        SymbolEntry sym = hashTable.get(name);
        while (sym != null) {
            if (sym.getName().equals(name))
                return sym;
            sym = sym.getHash();
        }
        return null;
    }

    public void entrySymbol(String name, Attributes attributes) {
        SymbolEntry oldSym = retrieveSymbol(name);

        if (attributes instanceof FuncAttributes) {
            if (oldSym != null && oldSym.getDepth() == 0) {
                error("Duplicate definition of ", name);
                return;
            }
            SymbolEntry newSym = createNewSymbol(name, attributes);
            newSym.setLevel((SymbolEntry) scopeDisplay.get(0));
            newSym.setDepth(0);
            scopeDisplay.set(0, newSym);

            if (oldSym == null) {
                addSym(newSym);
            } else {
                deleteSym(oldSym);
                addSym(newSym);
            }

            newSym.setVar(oldSym);
            return;
        }

        if (oldSym != null && oldSym.getDepth() == depth) {
            error("Duplicate definition of ", name);
            return;
        }
        SymbolEntry newSym = createNewSymbol(name, attributes);
        newSym.setLevel((SymbolEntry) scopeDisplay.get(depth));
        newSym.setDepth(depth);
        scopeDisplay.set(depth, newSym);

        if (oldSym == null) {
            addSym(newSym);
        } else {
            deleteSym(oldSym);
            addSym(newSym);
        }

        newSym.setVar(oldSym);
    }

    public Attributes declaredLocally(String name, String kind) {
        SymbolEntry sym = hashTable.get(name);
        while (sym != null
                && (sym.getDepth() != depth || !(sym.getAttributes().getKind()
                        .equals(kind))))
            sym = sym.getHash();

        if (sym == null)
            return null;
        return sym.getAttributes();
    }

    private SymbolEntry createNewSymbol(String name, Attributes attributes) {
        SymbolEntry sym = new SymbolEntry();
        sym.setName(name);
        sym.setAttributes(attributes);
        return sym;
    }

    private Boolean addSym(SymbolEntry sym) {
        SymbolEntry oldSym = hashTable.get(sym.getName());
        SymbolEntry sOldSym = oldSym;
        // while (oldSym != null && !oldSym.equals(sym))
        // oldSym = oldSym.getHash();

        // if (oldSym == null) {
        sym.setHash(sOldSym);

        sym.setPrehash(null);
        hashTable.put(sym.getName(), sym);
        return true;
        // }

        // return false;
    }

    private Boolean deleteSym(SymbolEntry sym) {
        SymbolEntry delSym = hashTable.get(sym.getName());
        while (delSym != null && !delSym.equals(sym))
            delSym = delSym.getHash();
        if (delSym == null)
            return false;
        delSym.setPrehash(null);
        delSym.setHash(null);
        return true;
    }

    private void error(String msg, String name) {
        System.out.println(msg + name);
    }

    public int getDepth() {
        return depth;
    }

    public void setDepth(int depth) {
        this.depth = depth;
    }

    public ArrayList getScopeDisplay() {
        return scopeDisplay;
    }

    public Hashtable<String, SymbolEntry> getHashTable() {
        return hashTable;
    }

}

enum Type {  INT, REAL, STRING, ARRAY, ID;
  
    public static Type getType(String type) {
        try {
            return valueOf(type.toUpperCase());
        } catch (Exception e) {
            return ID;
        }
    }
}
